#include <AST/expr/operator/BinaryOpAST.hpp>
#include <global.hpp>
#include <AST/expr/var/VarAST.hpp>

BinaryOp::BinaryOp(OpType opType, Expr *lhs, Expr *rhs)
    : opType(opType), lhs(lhs), rhs(rhs) {
    // 检查操作数是否足够
    if (opType != OpType::Semi) assert(lhs != nullptr && rhs != nullptr);
    else
        assert(lhs != nullptr); // 分号可以只有左操作数
    // TODO: 添加类型推导
    /*
    switch (opType) {
        case OpType::Semi:
            resultType = rhs != nullptr ? rhs->getType() : lhs->getType();
            break;

        default:
            resultType = nullptr;
            break;
    }
    */
}

llvm::Value *BinaryOp::codegen() {
    if (opType == OpType::Semi) {
        if (rhs == nullptr) return lhs->codegen();
        lhs->codegen();
        return rhs->codegen();
    }

    // 赋值运算符特殊处理
    if (opType == OpType::Assign) { return assignCodegen(); }

    Value *L = lhs->codegen();
    Value *R = rhs->codegen();
    if (L == nullptr || R == nullptr) return nullptr;
    // FIXME: 检查逻辑操作的类型提升是否合法
    Type *type = BasicConvert::binaryEnhance(L, R, builder);
    if (type == BasicType::BoolTy) {
        // 布尔类型运算
        return boolCodegen(L, R);
    } else if (type->isFloatingPointTy()) {
        // 浮点数运算
        return floatingCodegen(L, R, type);
    } else if (type->isIntegerTy()) {
        // 整数运算
        return integerCodegen(L, R, type);
    }

    return logError(
        "BinaryOp::codegen: unhandled opType: " + std::to_string(int(opType)));
}

llvm::Value *BinaryOp::floatingCodegen(Value *L, Value *R, Type *type) {
    switch (opType) {
        case OpType::Add:
            return builder.CreateFAdd(L, R, "faddTmp");
        case OpType::Sub:
            return builder.CreateFSub(L, R, "fsubTmp");
        case OpType::Mul:
            return builder.CreateFMul(L, R, "fmulTmp");
        case OpType::Div:
            return builder.CreateFDiv(L, R, "fdivTmp");

        case OpType::Lt:
            return builder.CreateFCmpOLT(L, R, "foltTmp");
        case OpType::Leq:
            return builder.CreateFCmpOLE(L, R, "foleTmp");
        case OpType::Eq:
            return builder.CreateFCmpOEQ(L, R, "foeqTmp");
        case OpType::Gt:
            return builder.CreateFCmpOGT(L, R, "fogtTmp");
        case OpType::Geq:
            return builder.CreateFCmpOGE(L, R, "fogeTmp");
        case OpType::Neq:
            return builder.CreateFCmpONE(L, R, "foneTmp");

        case OpType::Mod:
            return logError(
                "BinaryOp: trying to apply MOD with floating points");
        case OpType::Undefined:
        default:
            return logError(
                "BinaryOp: invalid or undefined operator: opType: "
                + std::to_string(int(opType)));
    }
}

llvm::Value *BinaryOp::integerCodegen(Value *L, Value *R, Type *type) {
    switch (opType) {
        case OpType::Add:
            return builder.CreateAdd(L, R, "addTmp");
        case OpType::Sub:
            return builder.CreateSub(L, R, "subTmp");
        case OpType::Mul:
            return builder.CreateMul(L, R, "mulTmp");
        case OpType::Div:
            return BasicType::isSigned(type)
                       ? builder.CreateSDiv(L, R, "sdivTmp")
                       : builder.CreateUDiv(L, R, "udivTmp");
        case OpType::Mod:
            return BasicType::isSigned(type)
                       ? builder.CreateSRem(L, R, "sremTmp")
                       : builder.CreateURem(L, R, "uremTmp");

        case OpType::Lt:
            return BasicType::isSigned(type)
                       ? builder.CreateICmpSLT(L, R, "sltTmp")
                       : builder.CreateICmpULT(L, R, "ultTmp");
        case OpType::Leq:
            return BasicType::isSigned(type)
                       ? builder.CreateICmpSLE(L, R, "sleTmp")
                       : builder.CreateICmpULE(L, R, "uleTmp");

        case OpType::Eq:
            return builder.CreateICmpEQ(L, R, "eqTmp");

        case OpType::Gt:
            return BasicType::isSigned(type)
                       ? builder.CreateICmpSGT(L, R, "sgtTmp")
                       : builder.CreateICmpUGT(L, R, "ugtTmp");

        case OpType::Geq:
            return BasicType::isSigned(type)
                       ? builder.CreateICmpSGE(L, R, "sgeTmp")
                       : builder.CreateICmpUGE(L, R, "ugeTmp");

        case OpType::Neq:
            return builder.CreateICmpNE(L, R, "neTmp");

        case OpType::Undefined:
        default:
            printf("integerBinOp\n");
            return logError(
                "BinaryOp: invalid or undefined operator: opType: "
                + std::to_string(int(opType)));
    }
}

llvm::Value *BinaryOp::boolCodegen(Value *L, Value *R) {
    switch (opType) {
        case OpType::And:
            return builder.CreateAnd(L, R, "andTmp");
        case OpType::Or:
            return builder.CreateSub(L, R, "orTmp");
        case OpType::Xor:
            return builder.CreateXor(L, R, "xorTmp");
        case OpType::Eq:
            return builder.CreateICmpEQ(L, R, "eqTmp");
        case OpType::Neq:
            return builder.CreateICmpNE(L, R, "neTmp");

        case OpType::Undefined:
        default:
            return logError(
                "BinaryOp: invalid or undefined operator: opType: "
                + std::to_string(int(opType)));
    }
}

llvm::Value *BinaryOp::assignCodegen() {
    Var *L = dynamic_cast<Var *>(lhs);
    if (L == nullptr)
        return logError("Left hand side of assignment must be a variable");
    // 右结合
    Value *R = rhs->codegen();
    if (R == nullptr) return nullptr;
    // 查询当前作用域符号表
    Value *variablePtr = L->refCodegen();
    if (variablePtr == nullptr)
        return logError(
            "BinaryOp::assignCodegen: unknown variable name: " + L->getName());

    builder.CreateStore(R, variablePtr);
    // XXX: 应该返回R还是load variable?
    return R;
    // return builder.CreateLoad(
    //     variablePtr->getType()->getPointerElementType(), variablePtr,
    //     L->getName()); // 这是一个指针
}
